import java.io.*;
import java.net.*;
import java.util.*;
import org.grlea.log.SimpleLogger;


class ForwarderThread implements Runnable {

	//Simple Log
	private static final SimpleLogger log = new SimpleLogger(ForwarderThread.class);
	protected static List pool = new LinkedList();  
	private Socket socket2c;	
	private String thisname;
  
	public ForwarderThread() {}
	
	public static void processRequest(Socket requestToHandle) {
		synchronized(pool) {
			log.info("Forwarder is processRequesting... on "+requestToHandle);
			pool.add(pool.size(), requestToHandle);
			pool.notifyAll();
		}
	}
	
	public void run() {
	  while(true) {		  
		  log.entry("run");//Simple Log
	      
		  synchronized(pool) {
	          while(pool.isEmpty()) {
	              try {
	                  pool.wait();
	                  //log.info("waiting"+pool.size());
	              }
	              catch(InterruptedException e) {
	                  e.printStackTrace(); 
	              }
	          }
	          socket2c = (Socket)pool.remove(0); 
	      }
	      handleConnection();
	      
	      log.exit("run");//Simple Log
	  }
	}
	
	public void  handleConnection() {
		String str = "UNKNOWN";
		boolean ok = true;
		
		BufferedReader	in2c;
		PrintWriter 	out2c;		
		BufferedReader	in2s;
		PrintWriter		out2s;
		
		Socket	socket2s;
		
		log.info("socket2c"+socket2c);
		
		try{
			//read from client
			socket2c.setSoTimeout(8000);
			in2c =  new BufferedReader( new InputStreamReader( socket2c.getInputStream()));
			
			out2c = new PrintWriter( new BufferedWriter( new OutputStreamWriter( socket2c.getOutputStream())), true);
			
			str = in2c.readLine();
			
			log.debugObject("from in2c:",in2c);
			log.info("from Client:"+str);
			
			try{
				
				socket2s = 	new Socket(Forwarder.addr, Forwarder.port_s);// FWD connect to server
				
				try{
					socket2s.setSoTimeout(8000);
					
					in2s = 
						new BufferedReader(
						  new InputStreamReader(
							socket2s.getInputStream()));
					
					// Enable auto-flush:
					out2s = 
						new PrintWriter(
						  new BufferedWriter(
							new OutputStreamWriter(
							  socket2s.getOutputStream())), true);
							  
					
					// send to server and fetch back information
					try {			
						out2s.println(str);//write to server
						log.info("to Server:"+str);
						do{
							str = in2s.readLine();//read from server
							log.info("from Server:"+str);
							out2c.println(str);//write to client
							log.info("to Client:"+str);
					
						}while(!str.endsWith("$"));
						
						
					} catch (InterruptedIOException e) {
						str = "FWD_TIMEOUT";
						System.err.println("FWD_TIMEOUT");
						log.errorException(e);
					} catch(IOException e) {
						str = "FWD_NET";
						System.err.println("FWD_NET");
						log.errorException(e);
					} catch(Exception e) {
						str = "FWD_OTHER";
						System.err.println("FWD_OTHER");
					}			  
					//20071029
					in2s.close();
					out2s.close();
					
				} catch (IOException e) {
					
					str = "FWD_CONNECT";
					System.err.println("FWD_CONNECT");
					
				} finally {
					
					try {
						socket2s.close();
					} catch (IOException e) {
						;
					}
				}
			} catch ( IOException e) {
				str = "FWD_CONNECT";
				System.err.println("FWD_CONNECT");
			}
		
			//20071029
			in2c.close();
			out2c.close();
			
		} catch (IOException e) {
			try {
				socket2c.close();
			} catch (IOException e1) {
				;
			}	
		}
	}
}






public class Forwarder {
	
	//Simple Log
	private static final SimpleLogger log = new SimpleLogger(Forwarder.class);
	
	protected static int maxConnections;  
	protected static InetAddress addr;
	protected static int port_s;
	private static int port_f;
	
	public Forwarder(int aListenPort, int maxConnections) {
		
	    port_f= aListenPort;
	    this.maxConnections = maxConnections;
	    
	}
	
	public void setThreads() {
		
	    for(int i=0; i<maxConnections; i++) {
	        ForwarderThread currentThread = new ForwarderThread();
	        new Thread(currentThread, "Thread " + i).start();
	        log.info("Forwarder started..."+i);
	    }
	}
	
	public void acceptConnections() {
		
      try {
          ServerSocket s = new ServerSocket(port_f, 5);
          Socket incomingConnection = null;
          while(true) {
        	  log.info("Forwarder is working...");
              incomingConnection = s.accept();
              handleConnection(incomingConnection);
          }
      }
      catch(BindException e) {
          System.out.println("BindException");
      }
      catch(IOException e) {
          System.out.println("IOException on "+port_f);
      }
	}
	
	
	protected void handleConnection(Socket connectionToHandle) {
      ForwarderThread.processRequest(connectionToHandle);
	}


	public static void main(String[] args) throws IOException 	{
		
      log.entry("main");//Simple Log
      log.info("This is the begin of main.");
      
      if (args.length!=4) {
			System.err.println("usage : java Forwarder <ForwarderPort> <ServerAddr> <ServerPort> <MaxConnections>");
			System.exit(-1);
      }
      
      port_f = Integer.parseInt(args[0]);
      addr = InetAddress.getByName(args[1]);
      port_s = Integer.parseInt(args[2]);
      maxConnections = Integer.parseInt(args[3]);
      
      //new pooled forwarder
      Forwarder forwarder = new Forwarder(port_f, maxConnections);
      forwarder.setThreads(); 
	  System.out.println("Forwarder started");
	  log.info("Forwarder started");
	  forwarder.acceptConnections();
	  
      log.exit("main");//Simple Log

	} 
} 

